package com.cnap.service.impl;

import cn.hutool.core.io.FileUtil;
import com.cnap.db.entity.ApplicationPO;
import com.cnap.db.entity.ProgressPO;
import com.cnap.db.mapper.ApplicationMapper;
import com.cnap.model.application.ApplicationDTO;
import com.cnap.model.application.ApplicationStatusEnum;
import com.cnap.model.application.ApplicationVO;
import com.cnap.model.application.QueryApplicationParam;
import com.cnap.model.application.UpdateApplicationParam;
import com.cnap.model.common.PageResult;
import com.cnap.model.common.ResultCode;
import com.cnap.model.exception.JsonException;
import com.cnap.model.progress.ProgressConstants;
import com.cnap.model.schedule.MicroServiceVO;
import com.cnap.model.schedule.StrategyStatusEnum;
import com.cnap.service.ApplicationService;
import com.cnap.service.FileService;
import com.cnap.service.IndicatorService;
import com.cnap.service.MicroSvcService;
import com.cnap.service.ProgressService;
import com.cnap.service.UserSysService;
import com.cnap.utils.PageUtils;
import com.cnap.utils.TokenUtils;
import com.github.pagehelper.PageInfo;
import com.github.pagehelper.page.PageMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 应用服务实现类
 *
 * @author xc
 **/
@Service
public class ApplicationServiceImpl implements ApplicationService {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationServiceImpl.class);

    private final ApplicationMapper applicationMapper;
    private final FileService fileService;
    private final ProgressService progressService;
    private MicroSvcService microSvcService;
    private final IndicatorService indicatorService;
    private final UserSysService userSysService;

    @Autowired
    public ApplicationServiceImpl(ApplicationMapper applicationMapper, FileService fileService,
                                  ProgressService progressService, IndicatorService indicatorService,
                                  UserSysService userSysService) {
        this.applicationMapper = applicationMapper;
        this.fileService = fileService;
        this.progressService = progressService;
        this.indicatorService = indicatorService;
        this.userSysService = userSysService;
    }

    @Autowired
    public void setMicroSvcService(MicroSvcService microSvcService) {
        this.microSvcService = microSvcService;
    }

    /**
     * 分页查询应用信息
     *
     * @param queryApplicationParam 请求参数
     * @return 结果
     */
    @Override
    public PageResult<ApplicationVO> findByPage(QueryApplicationParam queryApplicationParam) {
        PageMethod.startPage(queryApplicationParam.getPageNum(), queryApplicationParam.getPageSize());
        List<ApplicationPO> applicationPOS;
        // 普通用户只能查询自己的
        applicationPOS = applicationMapper.selectApplicationByParamWhitUser(queryApplicationParam, TokenUtils.getUserName());

        PageInfo<ApplicationVO> pageInfoToShow = getApplicationVOPageInfo(applicationPOS);
        return PageUtils.getPageResult(pageInfoToShow);
    }

    private PageInfo<ApplicationVO> getApplicationVOPageInfo(List<ApplicationPO> applicationPOS) {
        List<ApplicationVO> applicationVOS = new ArrayList<>(applicationPOS.size());
        for (ApplicationPO applicationPO : applicationPOS) {
            // 组装vo
            // 此处可优化
            // 单拉一个函数处理
            // 使用lambda
            ApplicationVO applicationVO = new ApplicationVO();
            // id
            applicationVO.setId(applicationPO.getId());
            // 名称
            applicationVO.setName(applicationPO.getName());
            // 大小
            applicationVO.setSize(applicationPO.getSize());
            // 分类
            applicationVO.setCategory(applicationPO.getCategory());
            // 版本
            applicationVO.setVersion(applicationPO.getVersion());
            // 云商
            applicationVO.setProvider(applicationPO.getUserName());
            // 更新时间
            applicationVO.setUpdateTime(applicationPO.getUpdateTime());
            // 描述
            applicationVO.setDescription(applicationPO.getDescription());
            // 标签
            applicationVO.setLabel(applicationPO.getLabel());
            // 二级分类
            applicationVO.setType(applicationPO.getType());
            // 状态
            applicationVO.setStatus(applicationPO.getStatus());
            // 部署资源池
            applicationVO.setDeployResourceInfo(applicationPO.getDeployResourceInfo());

            applicationVOS.add(applicationVO);
        }
        // 转换格式
        PageInfo<ApplicationPO> realPageInfo = new PageInfo<>(applicationPOS);
        PageInfo<ApplicationVO> pageInfoToShow = new PageInfo<>(applicationVOS);
        // 设置真正的分页数据
        PageUtils.setRealPageInfo(pageInfoToShow, realPageInfo);
        return pageInfoToShow;
    }

    /**
     * 分页查询应用信息
     *
     * @param queryApplicationParam 请求参数
     * @return 结果
     */
    @Override
    public PageResult<ApplicationVO> findByPageForSplit(QueryApplicationParam queryApplicationParam) {
        PageMethod.startPage(queryApplicationParam.getPageNum(), queryApplicationParam.getPageSize());
        List<ApplicationPO> applicationPOS;
        // 普通用户只能查询自己的
        applicationPOS = applicationMapper.selectByParamForSplit(queryApplicationParam, TokenUtils.getUserName());

        PageInfo<ApplicationVO> pageInfoToShow = getApplicationVOPageInfo(applicationPOS);
        return PageUtils.getPageResult(pageInfoToShow);
    }

    /**
     * 保存应用信息(已上传应用包)
     *
     * @param applicationDTO 上传模型
     * @param id             appId
     */
    @Override
    public void saveApplicationWithUploadedStatus(ApplicationDTO applicationDTO, String id) {
        ApplicationPO applicationPO = applicationDTO.toPO();
        // 使用之前创建的id
        applicationPO.setId(id);
        // 状态
        applicationPO.setStatus(ApplicationStatusEnum.UPLOADED_FILE.getValue());
        int result = applicationMapper.insertApplication(applicationPO);
        if (result != 1) {
            LOGGER.error("save application to db error, name={}", applicationDTO.getApplicationName());
            throw new JsonException(500, "内部错误");
        }
        // 插入指标实例移至微服务创建处
    }

    /**
     * 上传应用
     *
     * @param applicationDTO 上传模型
     * @return 模型id
     */
    @Override
    @Transactional
    public String uploadApplication(ApplicationDTO applicationDTO) {
        // TODO 校验配额
        if (!userSysService.checkHasQuota(TokenUtils.getUserName())) {
            System.out.println("das");
            throw new JsonException(400, "配额校验失败");
        }
        /*
        上传流程
        初始化上传，前端需要获取压缩文件总大小，文件哈希值以及业务相关信息，通过初始化接口上传信息，接口返回id
        根据总大小以及分片大小，将文件分成若干片
        分片文件上传，并带有步骤1返回的id，表明是这个文件的分片信息
        循环步骤3，直至所有分片成功上传
        发送合并请求，带有步骤1返回的id
         */
        ApplicationPO applicationPO = applicationDTO.toPO();
        // 判断当前用户是否已上传同名同版本的应用
        // 当前平台不允许同一用户上传同名同版本应用
        List<ApplicationPO> appsWithSameNameAndVersion =
                applicationMapper.selectApplicationByNameAndVersionWithUser(applicationPO.getName(),
                        applicationPO.getVersion(), applicationPO.getUserName());
        if (Objects.nonNull(appsWithSameNameAndVersion) && !appsWithSameNameAndVersion.isEmpty()) {
            LOGGER.warn("user {} has upload same name and version application(name={}, version={})",
                    applicationPO.getUserName(), applicationPO.getName(), applicationPO.getVersion());
            throw new JsonException(500, "已上传同名同版本应用");
        }
        // 在合并文件成功后插入
        // 此处使用applicationPO的id作为fileInfo的id
        String id = fileService.initFileInfo(applicationDTO.getFileInitReq(), applicationPO.getId());
        LOGGER.info("init file info, id={}", id);
        // 新建进度条，表示上传
        ProgressPO progressPO = new ProgressPO();
        // id
        progressPO.setId(id);
        // 进度
        progressPO.setPercent(0);
        // 步骤
        progressPO.setStage(ProgressConstants.STAGE_UPLOAD_PATCH);
        // 类型
        progressPO.setType(ProgressConstants.TYPE_UPLOAD);
        boolean progressResult = progressService.initProgress(progressPO);
        if (!progressResult) {
            LOGGER.error("init progress error");
            throw new JsonException(500, "内部错误");
        }
        // 插入指标实例，若上传失败，则在失败时删除所有实例
        // 批量插入指标实例
        LOGGER.info("save new application={}", applicationDTO);
        // 异步的，需要设置此时状态为 未上传应用包
        applicationPO.setStatus(ApplicationStatusEnum.UN_UPLOAD_FILE.getValue());
        applicationMapper.insertApplication(applicationPO);
        return id;
    }

    /**
     * 修改应用信息
     *
     * @param updateApplicationParam 参数
     * @return 结果
     */
    @Override
    @Transactional
    public boolean updateApplication(UpdateApplicationParam updateApplicationParam) {
        ApplicationPO oldApp = applicationMapper.selectById(updateApplicationParam.getId());
        // 校验id是否存在
        if (Objects.isNull(oldApp)) {
            LOGGER.error("can't query application in db, id={}", updateApplicationParam.getId());
            throw new JsonException(400, "参数错误");
        }
        // 判断是否有权限
        if (!oldApp.getUserName().equals(TokenUtils.getUserName())) {
            LOGGER.error("user {} has no permissions to update application {}", TokenUtils.getUserName(),
                    updateApplicationParam.getId());
            throw new JsonException(ResultCode.FORBIDDEN);
        }
        int result = applicationMapper.updateApplication(updateApplicationParam);
        return result == 1;
    }

    /**
     * 根据id删除其余资源（分片文件，合并文件，解压缩文件，数据库信息）
     *
     * @param applicationId 应用id
     */
    @Override
    @Transactional
    public void deleteOtherUploadResourcesByApplicationId(String applicationId) {
        // 删除fileInfo 删除patchInfo
        fileService.deleteFileInfoAndPatchInfoById(applicationId);
        // 删除progress
        progressService.deleteProgressByApplicationId(applicationId);
        // 删除本地文件
        String toDeletePath = fileService.getSavePath(applicationId);
        if (FileUtil.exist(toDeletePath)) {
            boolean result = FileUtil.del(toDeletePath);
            if (!result) {
                LOGGER.error("fail to delete path {}", toDeletePath);
            }
        }
    }

    /**
     * 删除解压缩文件和数据库信息
     *
     * @param applicationId 应用id
     */
    @Override
    public void deleteUnzipFileAndDbInfo(String applicationId) {
        // 删除本地文件
        String toDeleteUnzipPath = fileService.getSavePath(applicationId) + File.separator + "unzip";
        if (FileUtil.exist(toDeleteUnzipPath)) {
            boolean result = FileUtil.del(toDeleteUnzipPath);
            if (!result) {
                LOGGER.error("fail to delete unzip path {}", toDeleteUnzipPath);
            }
        }
        // 删除镜像，若有
        boolean microserviceResult = microSvcService.deleteMicroServiceImagesWithAppId(applicationId);
        if (!microserviceResult) {
            LOGGER.error("delete image fail, appId={}", applicationId);
        }
    }

    /**
     * 根据id删除所有上传时的资源
     *
     * @param applicationId 应用id
     */
    @Override
    @Transactional
    public void deleteAllUploadResourcesByApplicationId(String applicationId) {
        // 删除其余资源
        deleteOtherUploadResourcesByApplicationId(applicationId);
        applicationMapper.deleteApplicationById(applicationId);
    }

    /**
     * 根据id更新应用状态
     *
     * @param applicationId 应用id
     * @param status        状态 0：初始，1：页面显示
     */
    @Override
    public void updateApplicationStatus(String applicationId, int status) {
        applicationMapper.updateApplicationStatusById(applicationId, status);
    }

    /**
     * 根据id查询应用
     *
     * @param appId 应用id
     * @return 结果
     */
    @Override
    public ApplicationPO selectByAppId(String appId) {
        return applicationMapper.selectById(appId);
    }

    /**
     * 根据id删除应用信息
     *
     * @param applicationId 应用id
     * @return 结果
     */
    @Override
    @Transactional
    public boolean deleteApplicationById(String applicationId) {
        ApplicationPO oldApp = applicationMapper.selectById(applicationId);
        // 判断权限
        if (!oldApp.getUserName().equals(TokenUtils.getUserName())) {
            LOGGER.error("user {} has no permissions to delete application {}", TokenUtils.getUserName(),
                    applicationId);
            throw new JsonException(ResultCode.FORBIDDEN);
        }

        // 有部署微服务的应用无法删除
        List<MicroServiceVO> microServicePOS = microSvcService.selectMicroServicesByAppId(applicationId);
        for (MicroServiceVO microServiceVO : microServicePOS) {
            int status = Integer.parseInt(microServiceVO.getStatus());
            // 已启动，暂停，启动中的不可删除
            if (StrategyStatusEnum.APPLIED.getStatus() == status
                    || StrategyStatusEnum.PAUSED.getStatus() == status
                    || StrategyStatusEnum.STARTING.getStatus() == status) {
                LOGGER.error("application id={} has deployed or paused micro service", applicationId);
                throw new JsonException(500, "该应用已有微服务启用，请先暂停并删除所有微服务");
            }
        }

        // 1. 通知算法引擎停止并删除策略
        // 不需要通知算法引擎了，因为此时平台也拿不到集群信息了
        // 可能需要在此处通知算法引擎删除命名空间

        // 2. 删除harbor镜像
        boolean deleteImagesResult = microSvcService.deleteMicroServiceImagesWithAppId(applicationId);
        if (!deleteImagesResult) {
            return false;
        }
        // 3. 删除本地微服务信息，包括策略信息
        microSvcService.deleteMicroServiceAndStrategyWithAppId(applicationId);
        // 4. 删除应用及上传信息
        deleteAllUploadResourcesByApplicationId(applicationId);

        // TODO 更新配额
        userSysService.updateQuota(1, TokenUtils.getUserName(), "release");

        return true;
    }
}
